#ifndef RPC_HPP
#define RPC_HPP
#include <stdexcept>
#include <string>
#include <map>
#include "StreamBuffer.hpp"
#ifdef THREADED
#include <pthread.h>
#endif

class RemoteHost;


class MissingParam
	:public std::runtime_error
{
public:
	MissingParam(const char *name);
};

class NoSuchFunc
	:public std::runtime_error
{
public:
	NoSuchFunc(const char *name);
};

class ProtocolError
	:public std::runtime_error
{
public:
	ProtocolError(const char *description);
};

/////////////////////////////////////////////////////////////////////////////

class RPCParams
{
public:
	RPCParams();
	RPCParams(std::string str);
	std::string toString() const;
	bool operator==(const RPCParams &rhs) const;
	
	void set(const char *name, bool val);
	void set(const char *name, int val);
	void set(const char *name, const char *val);
	void set(const char *name, std::string val);
	void set(const char *name, int64_t val);
	
	bool hasArg(const char *name) const;
	
	bool getBool(const char *name) const throw(MissingParam);
	int getInt(const char *name) const throw(MissingParam);
	const char *getString(const char *name) const throw(MissingParam);
	int64_t getInt64(const char *name) const throw(MissingParam);
	
private:
	typedef std::map<std::string,std::string> ParamsType;
	ParamsType params;
};

/////////////////////////////////////////////////////////////////////////////

class StreamSender
{
public:
	StreamSender(RemoteHost *destination, RPCParams *params);
	virtual ~StreamSender();
	
	void send(int len, const char *data);
	void close();
	bool isClosed() const;
	virtual void disconnected();
	
protected:
	RemoteHost *destination;
	int streamId;
	bool closed;
};

/////////////////////////////////////////////////////////////////////////////

class StreamReceiver
{
public:
	StreamReceiver(RemoteHost *source, const RPCParams *params);
	virtual ~StreamReceiver();
	
	void close();
	bool isClosed();
	virtual void recv(int len, const char *data) = 0;
	virtual void onClose();
	virtual void disconnected();
	
protected:
	RemoteHost *source;
	int streamId;
	bool closed;
};

/////////////////////////////////////////////////////////////////////////////

class RemoteHost
{
public:
	RemoteHost();
	virtual ~RemoteHost();
	
	virtual void sendData(int len, const char *data) = 0;
	
	void receivedData(int len, const char *data);
	void call(const char *name, const RPCParams *params);
	
private:
	friend class StreamSender;
	friend class StreamReceiver;
	
	int handleData(int len, const char *data);
	int handleChunk(const char *data, const char *end);
	void handleLine(const char *line, const char *end);
	
	int newStreamId();
	
	void enterStream(int streamId);
	void exitStream();
	
	void addStreamReceiver(int streamId, StreamReceiver *receiver);
	StreamReceiver *getStreamReceiver(int streamId);
	void closeStream(int streamId);
	
	void sendStream(int streamId, int len, const char *data);
	void sendCloseStream(int streamId);
	
	StreamBuffer incomingData;
	std::map<int, StreamReceiver*> streamReceivers;
	StreamReceiver *incomingStream;
	
	int maxStreamId;
	int sendingStreamId;
	
#ifdef THREADED
	#define RPC_LOCK(mutex) pthread_mutex_lock(&mutex)
	#define RPC_UNLOCK(mutex) pthread_mutex_unlock(&mutex)
	pthread_mutex_t sendMutex;
	pthread_mutex_t recvMutex;
	pthread_mutex_t streamsMutex;
#else
	#define RPC_LOCK(mutex)
	#define RPC_UNLOCK(mutex)
#endif
};

/////////////////////////////////////////////////////////////////////////////

#define RPC_FUNCTION(name) \
	inline void name(RPCParams *params) \
		{ rpcCall(#name, params); }

#define RPC_HANDLER(name) \
	void handle_##name(const RPCParams *params, RemoteHost *host); \
	void init_##name() \
		{ rpcAddHandler(#name, handle_##name); } \
	StartupCaller init_##name##_caller(init_##name); \
	void handle_##name(const RPCParams *params, RemoteHost *host) \

typedef void (*RPCFunc)(const RPCParams *params, RemoteHost *host);
void rpcAddHandler(const char *name, RPCFunc func);
void rpcCallLocal(const char *name, const RPCParams *params, RemoteHost *host);

#endif
